This post will demonstrate how to build a simple Clojure Web application, with Compojure, that will use Incanter to generate a sample from a normal distribution with parameters (size, mean, and standard deviation) provided by the user, and then return a PNG image of the histogram of the data.
This example is simple, but it demonstrates how to dynamically generate an Incanter chart and pass it directly to the Web client as a PNG image.
For more information on the Compojure Web framework, visit its Github repository, its Google group, its WikiBooks site, and this quick tutorial.
I will use the Leiningen build tool to download dependencies, compile the application, and package it up as jar file. For more information on installing and using Leiningen, see my previous post.
Let’s start off by creating a project directory, I’ll call it incanter-webapp
, and give it a src
subdirectory. Then create the following Leiningen project.clj file in the project directory.
(defproject incanter-webapp "0.1.0" :description "A simple Incanter web-app." :dependencies [[incanter "1.2.1-SNAPSHOT"] [compojure "0.3.2"]] :main simple_web_app)
This project file includes two dependencies, Incanter and Compojure, and indicates that the -main
function for the project is in the file simple_web_app.clj
in the src
subdirectory of the project.
Next create the file called simple_web_app.clj in the src
subdirectory.
(ns simple_web_app (:gen-class) (:use [compojure] [compojure.http response] [incanter core stats charts]) (:import (java.io ByteArrayOutputStream ByteArrayInputStream)))
Note that the namespace for this file is simple_web_app
with underscores like in the file name and in the :main line of the project.clj
file.
In Compojure, you need to define routes, which in this case associate the function sample-form
with “/”, and the function gen-samp-hist-png
with “/sample-normal”.
(defroutes webservice (GET "/" sample-form) (GET "/sample-normal" (gen-samp-hist-png request (params :size) (params :mean) (params :sd))))
The function gen-samp-hist-png
takes four arguments, a request object, the sample size, the population mean, and the population standard deviation, and then updates the Compojure response object to include an Incanter histogram of a sample from a normal distribution with the given parameters.
Now define the gen-samp-hist-png
function; start by converting the string-arguments, passed in from the params
object, into numbers.
(defn gen-samp-hist-png [request size-str mean-str sd-str] (let [size (if (nil? size-str) 1000 (Integer/parseInt size-str)) m (if (nil? mean-str) 0 (Double/parseDouble mean-str)) s (if (nil? sd-str) 1 (Double/parseDouble sd-str))
Each argument has a default value, 1000 for size, 0 for mean, and 1 for the standard deviation (sd).
Next generate the sample from the normal distribution using Incanter’s sample-normal
function with the given size, mean, and standard deviation, and then create a histogram of the resulting data.
samp (sample-normal size :mean m :sd s) chart (histogram samp :title "Normal Sample" :x-label (str "sample-size = " size ", mean = " m ", sd = " s))
I’ve used the x-label
option of the histogram
function to customize the x-axis label so that it includes the given sample size, mean, and standard deviation.
Next, we need to convert the histogram into a byte array, and feed that into a ByteArrayInputStream
that can be passed to Compojure’s update-response
function, along with a header that sets the Content-Type to “image/png”.
out-stream (ByteArrayOutputStream.) in-stream (do (save chart out-stream) (ByteArrayInputStream. (.toByteArray out-stream))) header {:status 200 :headers {"Content-Type" "image/png"}}] (update-response request header in-stream)))
The above code is the key to sending a dynamically generated chart directly to the client. In addition to a filename, an OutputStream
can be passed to Incanter’s save
function; in this case, we use a ByteArrayOutputStream
, which is then converted to a byte array that is used to initialize the ByteArrayInputStream
that is passed to update-response
.
We can create a Web form for submitting requests to /sample-normal
. The following function is a helper function that creates a simple html page skeleton.
(defn html-doc [title & body] (html (doctype :html4) [:html [:head [:title title]] [:body [:div [:h2 [:a {:href "/"} "Generate a normal sample"]]] body]]))
Now define the sample-form
function, which will generate a Web form using html-doc
. This function was associated with “/” earlier using the defroutes
macro.
(def sample-form (html-doc "sample-normal histogram" (form-to [:get "/sample-normal"] "sample size: " (text-field {:size 4} :size) "mean: " (text-field {:size 4} :mean) "sd: " (text-field {:size 4} :sd) (submit-button "view"))))
Now, we need to create a -main
function that will be called when the incanter-webapp.jar
is executed. This function will call Compojure’s run-server
function, starting the built-in Jetty server on port 8080.
(defn -main [& args] (run-server {:port 8080} "/*" (servlet webservice)))
Now we can use Leiningen to download and install all the necessary dependencies, including Incanter and Compojure, and then build and package the app.
$ lein deps
$ lein compile
$ lein uberjar
Finally, to start the server, run
$ java -jar incanter-webapp-standalone.jar
and visit http://localhost:8080
to submit a request, or go directly to the sample-normal app by constructing a URL like the following:
http://localhost:8080/sample-normal?size=500&mean=100&sd=10
Which returns a PNG image of a histogram like this:
The complete code for this example can be found here, and the Leiningen project file can be found here.
Pingback: Destillat KW49-2009 | duetsch.info - GNU/Linux, Open Source, Softwareentwicklung, Selbstmanagement, Vim ...
Nice example.
It worked out of the box for me (but if anyone is cutting/pasting the code chunks from this web page, rather than the raw code file, remember to remove ‘amp;’ from ‘&’ (two occurrances), as well as the third right-bracket ‘)’ at end of the line ‘(Double/parseDouble sd-str)))’ before compiling).
Now I just have to learn/understand the code. :-(
Thanks for posting.
Hi John,
Thanks for catching those bugs, I just fixed them.
To avoid any other weirdness introduced by cutting/pasting html, it’s probably best to grab the complete code from the link at the end of the post, or from the latest Incanter distribution in the examples/blog/projects/simple_web_app/ directory.
Hi,
Good simple tutorial.
One more thing that might be useful.
How would you recommend developing at a REPL with this setup?
As it stands and code changes require a rebuild and restart of the server.
thanks,
James
Hi James,
Check out Lau Jensen’s screencast on that very subject: http://www.bestinclass.dk/index.php/2009/12/dynamic-interactive-webdevelopment/
David
Hi David,
yeah I’ve seen that video. It’s a good demo but it’s not going into the detail of how you would get slime to pick up the clojure version and all the other jars downloaded by lein.
For command line usage I’ve put together this variation of the common bash script, might be useful for others…
http://gist.github.com/267746
If I figure out how to do the same for emacs+slime I’ll post it here.
thanks,
James
Hi James,
To set up interactive development of this app if you’re using Leiningen and Swank, just run:
$ lein deps
$ lein compile
$ lein swank
(I actually had to change my Incanter dependency to [incanter “1.0-SNAPSHOT”], instead of [incanter “1.0-master-SNAPSHOT”] because lein couldn’t find the later version for some reason.)
Then from emacs, connect to the Swank server with ‘M-x slime-connect’, then evaluate the example file and start with server from the REPL with:
(simple-web-app/-main)
Make sure the app is working correctly by visiting: http://localhost:8080
Now you should be able to make changes to the running app.
Good luck,
David
Thanks David,
I’ll give it a whirl.
James.
Works perfect.
Thanks again.
James
oops.. i just noticed you already had another post up about this exact thing. i’m a little behind on reading the blog :)
This does NOT work with newer versions of Compojure.
That is true, Compjure 0.4 was a complete refacter of the library.
Nice one.
I was looking for a Compojure kick-start and this article gives me what I need to understand Compojure.
Thank you,
—
Bahman
Hi,
I know this post is almost one year old, but would someone help me to run this? I downloaded Incanter and tried to build the corresponding example. “lein compile” fails like this:
“Compiling simple_web_app
Exception in thread “main” java.io.FileNotFoundException: Could not locate clojure/contrib/seq_utils__init.class or clojure/contrib/seq_utils.clj on classpath: (control.clj:9)”
Any ideas?
I managed to update the code in this post for use with the present libraries.
In project.clj, dependencies are as follow:
:dependencies [[org.clojure/clojure "1.2.0"]
[org.clojure/clojure-contrib "1.2.0"]
[compojure "0.5.2"]
[ring/ring-jetty-adapter "0.3.1"]
[hiccup "0.3.0"]
[incanter "1.2.3-SNAPSHOT"]]
The function that generates the graphic should return the whole header:
{:status 200
:headers {"Content-Type" "image/png"}
:body in-stream}))
Finally, main is changed to:
(defn -main [& args]
(run-jetty example {:port 8080}))
The HTML generation has changed a bit (now it is dome by hiccup). Here is a good example of the new library usage.